/**
 * FATtER - Fat Extended Release
 * This release allows the user control not only over what is being faded but
 * which properties of the classes are being faded.
 * Props to Adam Michela of http://www.axentric.com/aside/fat/ This script started out 
 * with his code and a lot of his code still resides in it.
 *
 * Goals of FATtER
 * + Control over fadiing both the foreground and background colors
 * + Fade delay.  Delay fading so that different elements start fading at different times.
 * - Build a sparkle
 * 
 * ToDo:
 * - Move build_steps into Fatter and make build_steps take a parameter of an object.
 *   This should reduce the overall size of each object and the complexity of the script but it
 *   will mean that once steps are built, they are built. The object can't change them.
 * - Rebuild on Prototype
 *
 * The format for the class name is:
 *   fade-fromColor-toColor-duration-delay-cycles
 *   # == current color in a color field.
 *   # == unlimited in the cycles field.
 *   
 * EXAMPLES:
 *   fade - traditional              - Traditional FYT
 *   <span id="8" class="fade">This is the traditional FYT.</span>
 *   
 *   fade-#-FFFFFF-color             - Fade to white foreground.
 *   <span id="9" class="fade-#-FFFFFF-color">This will go away.</span>
 *   
 *   fade-#-FFFFFF-color-5000-1000   - pause 5 seconds and then fade to white.
 *   <span id="10" class="fade-#-FFFFFF-color-5000-1000">This will delay and then go away.</span>
 *   
 *   fade-#-FFFFFF-color-1000-100-10 - "Soft Blink 5 complete cycles."
 *   <span id="1" class="fade-#-FFFFFF-color-1000-500-10">This will blink 5 times.</span>
 *   
 * The className HAS TO START WITH fade
 * The element HAS TO HAVE A UNIQUE ID
 * 
 * @name      The Fade Anything Technique Extended Release
 * @namespace http://www.calevans.com/view.php/page/fatter
 * @version   2.0.1
 * @author    Cal Evans <cal@calevans.com>
 *
 * 01/17/2006 - Cal
 * fixed a problem setting the timer. The ID of the element was not being enclosed in 
 * quotes so it was not resolving properly.
 */

/**
 * Quick test class. If the script can't instantiate this then there is something wrong.
 */
var Fatter_test = {};


/**
 * Main class for FATtER. Each element under FATtERs control will have one FC object.
 * @parameter string elementID the ID used to identify this object in the DOM.
 */
function Fatter_Control(elementID) {
	/*
	 * These two functions are needed to init the properties of the object so they go first. 
	 */

    /**
     * Gets the property's color from the specified color property.
     * @return string the requested color string.
     */
    this.get_property = function () {
            var thisObject = document.getElementById(this.elementID);
            while(thisObject)  
            {
                    var computedStyle;
        if (window.getComputedStyle) computedStyle = window.getComputedStyle(thisObject,null).getPropertyValue(this.propertyToFade);
                    if (thisObject.currentStyle) computedStyle = thisObject.currentStyle.color;
                    if ((computedStyle != "" && computedStyle != "transparent") || thisObject.tagName == "BODY") { break; }
                    thisObject = thisObject.parentNode;
            }  // while(thisObject)
            if (computedStyle == undefined || computedStyle == "" || computedStyle == "transparent") computedStyle = "#FFFFFF";
            var rgb = computedStyle.match(/rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/);
            if (rgb) computedStyle = this.make_hex(parseInt(rgb[1]),parseInt(rgb[2]),parseInt(rgb[3]));
            return computedStyle;

    } // this.get_property


    /**
     * convert rgb to hex
     * @parameter int r - the red value to convert
     * @parameter int g - the green value to convert   
     * @parameter int b - the blue value to convert
     * @return string the hex value of the color.
     */
    this.make_hex = function(r,g,b) {
            r = r.toString(16); if (r.length == 1) r = '0' + r;
            g = g.toString(16); if (g.length == 1) g = '0' + g; 
            b = b.toString(16); if (b.length == 1) b = '0' + b;
            return "#" + r + g + b;
    } // this.make_hex

	
	/*
	 * Init objects properties.
	 * Most of the defaults here are to setup for a traditional FYT
	 */
	this.elementID          = elementID;
	this.propertyToFade     = "backgroundColor";
	this.startColor         = "#FFFF33";
	this.endColor           = this.get_property(elementID,this.propertyToFade);
	this.fps                = 30;
	this.duration           = 3000;
	this.cycles             = 1;
	this.delay              = 0;
	this.steps              = new Array();
	this.currentStep        = -1;
	this.interval           = 0;
	this.direction          = 1;
	this.currentCycle       = 0;
	this.currentDirection   = 1;


	/*
	 * generates the steps array.
	 */
	this.buildSteps = function() {
		var frames   = Math.round(this.fps * (this.duration / 1000));
		var interval = this.duration / frames;
		var frame    = 0;
		var r,g,b,h;
	
		/*                
		 * sanity check the parameters
		 */
		this.startColor = (this.startColor.substr(0,1)!='#'?'#':'')+this.startColor;
		this.endColor   = (this.endColor.substr(0,1)!='#'?'#':'')+this.endColor;
		this.cycles     = this.cycles=="#"?-1:this.cycles;
		
		if (this.startColor=='#') this.startColor = this.get_property(this.elementID, 'color');
		if (this.endColor=='#')   this.endColor   = this.get_property(this.elementID, 'backgroundColor');
		
		if (this.startColor.length < 7) this.startColor += this.startColor.substr(1,3);
		if (this.endColor.length < 7) this.endColor += this.endColor.substr(1,3);

	
		/*
		 * Break the colors into integers.
		 */
		var rf = parseInt(this.startColor.substr(1,2),16);
		var gf = parseInt(this.startColor.substr(3,2),16);
		var bf = parseInt(this.startColor.substr(5,2),16);
		var rt = parseInt(this.endColor.substr(1,2),16);
		var gt = parseInt(this.endColor.substr(3,2),16);
		var bt = parseInt(this.endColor.substr(5,2),16);
		
		while (frame < frames) {
			/*
			 * For each frame, compute the next color in the list. 
			 */
			r = Math.floor(rf * ((frames-frame)/frames) + rt * (frame/frames));
			g = Math.floor(gf * ((frames-frame)/frames) + gt * (frame/frames));
			b = Math.floor(bf * ((frames-frame)/frames) + bt * (frame/frames));
			h = this.make_hex(r,g,b);
			this.steps[frame++] = h;
		} // while (frame < this.frames) 
	
		/*
		 * Finally, set the final color.
		 */
		if (h!=this.endColor) this.steps[frame]=this.endColor;
	} // this.buildSteps = function() 



	/*
	* Start the effect. If there is a delay, start will delay 
	* itself by that amount and then start the effect.
	*/
	this.start = function() {
		this.reset();
		
		if (this.delay>0) {
			//alert("window.fatter[\""+this.elementID+"\"].next();");
			setTimeout("window.fatter[\""+this.elementID+"\"].next();", this.delay);
		} else {
			this.next();
		}
	} // this.start = function()


	/*
	* Reset the effect.
	*/
	this.reset = function() {
		this.currentCycle     = this.cycles;
		this.currentDirection = this.direction;
		this.currentStep      = 0;
	} // this.reset


	/*
	* Stop the effect. Either prematurely or as the last step.
	*/
	this.stop = function() {
		this.currentStep  = this.steps.length;
		this.currentCycle = 0;
		this.set_property(this.steps[this.steps.length]);
	} // this.stop = function()
	
	
	/*
	* Make the next step and queue the next timer.
	*/
	this.next = function() {
		/*
		 * If we've reacrd one end r the other of the stpes array then we need
		 * to reverse directions.
		 */
		if ((this.currentStep<0) || (this.currentStep>=this.steps.length)) {
			this.currentCycle     -= this.currentCycle!=0?1:0;
			this.currentDirection = this.currentDirection*-1;
			this.currentStep      = this.currentStep+this.currentDirection;
			
			/*
			 * If we have a delay then set the timer for it and bail.
			 */
			if (this.delay>0) {
				this.currentStep = this.currentStep+this.currentDirection;
				setTimeout("window.fatter[\""+this.elementID+"\"].next();", this.delay);
				return;
			} // if (this.delay>0)
		} // if (this.currentStep>=this.steps.length)
		
		
		/*
		 * Finally, if the current cycle is not 0 (meaning we have a positive
		 * number and are still counting down or we have a negative number
		 * which means infinite loop) set the next step and the next timer.
		 */
		if (this.currentCycle!=0) {
			this.set_property(this.steps[this.currentStep]);
			this.currentStep = this.currentStep+this.currentDirection;
			setTimeout("window.fatter['"+this.elementID+"'].next();", this.interval);
		} // if (this.currentCycle!=0)
	} // this.next = function()



        /*
         * Sets the property's color to the newly specified value.
         */        
        this.set_property = function (newValue) {
                var thisObject = document.getElementById(this.elementID);
                thisObject.style[this.propertyToFade] = newValue;
        } // this.set_property = function (newValue)

} // Fatter_control

/**
 * Static class that is used to kick things off. This holds the functions
 * necessary to scan the page for fade classes and build the objects. It is
 * never instantiated, it's methods are called statically.
 */
var Fatter = {
	
	/**
	 * Main page scan function.
	 */
	fade_all : function ()
	{
		/*
		 * Get a list of all the objects in the DOM
		 */
		var allTagsArray = document.getElementsByTagName("*");

		/*
		 * Walk the list.
		 */
		for (var i = 0; i < allTagsArray.length; i++)
		{
			var thisTag = allTagsArray[i];
			/*
			 * If the class name begins with fade, build a fade.
			 */
			if (thisTag.className.substring(0,4)=="fade") {
				/*
				 * anything after the first - in the class name is a parameter.
				 */
				fadeParameters=this.explode('-',thisTag.className);
				
				/*
				 * We only operate on objects that have an ID. This is so we
				 * can find it later to set the property.
				 */
				if (thisTag.id) {
					x = new Fatter_Control(thisTag.id);
					window.fatter[thisTag.id] = new Fatter_Control(thisTag.id);
					if (fadeParameters.length>1) window.fatter[thisTag.id].startColor     = fadeParameters[1];
					if (fadeParameters.length>2) window.fatter[thisTag.id].endColor       = fadeParameters[2];
					if (fadeParameters.length>3) window.fatter[thisTag.id].propertyToFade = fadeParameters[3];
					if (fadeParameters.length>4) window.fatter[thisTag.id].duration       = fadeParameters[4];
					if (fadeParameters.length>5) window.fatter[thisTag.id].delay          = fadeParameters[5];
					if (fadeParameters.length>6) window.fatter[thisTag.id].cycles         = fadeParameters[6];
					window.fatter[thisTag.id].buildSteps();
				} // if (thisTag.id)
			} else if (thisTag.className.substring(0,4)=="sparkle") {
				/*
				 * FUTURE: Build a sparkle here
				 */
			} // if (thisTag.className.substring(0,4)=="fade")
		} // for (var i = 0; i < a.length; i++)
		return true;
	},


	/*
	 * Called by fade_all, breaks us the className returns an array of the
	 * parts. This works exactly like PHP's explode.
	 * 
	 * @parameter string seperator the seperator to break on.
	 * @parameter string inputString the input string to break.
	 * @return array the array broke apart.
	 *
	 */
	explode : function (separator,inputString) {
		/*
		 * Init needed variables
		 */
		inputString  = new String(inputString);
		separator    = new String(separator);
		returnArray  = new Array(1);
		count        = 0;
	
		
		/*
		 * Make sure we have a valid seperator
		 */
		if(separator == "undefined") separator = "-";
	
	
		/*
		 * Of, if there are no seperators in the inputString then return the
		 * inputString
		 */
		if (inputString.indexOf(separator)==-1) {
			returnArray[0]=inputString;
			return returnArray;
		} // if (inputString.indexOf(separator)==-1)
	
	
		/*
		 * Splitter loop. Grab the part before the seperator, store it, remove
		 * it form the full string, repeat as necessary.
		 */
		while (inputString.indexOf(separator)!=-1) {
			returnArray[count++] = inputString.substring(0,inputString.indexOf(separator));
			inputString = new String(inputString.substring(returnArray[count-1].length+1));
		} // while (inputString.indexOf(separator)!=-1)
	
	
		/*
		 * Grab the remainder and stick it on the end of the array.
		 */
		returnArray[count]=inputString;
		return returnArray;
	} // explode : function (separator, inputString)

} // var Fatter




/*
 * Forces the code to be executed at page load.
 */
window.onload = function () {
	window.fatter = new Object();
	Fatter.fade_all();
	for (var testKey in window.fatter) {
		window.fatter[testKey].start();
	}
}

